/* eslint-disable no-unused-expressions */
/**
 * 项目版本分离器
 *
 * Created by snowingsea on 2021/01/15.
 */
const fs = require('fs-extra');
const path = require('path');
const randomstring = require('randomstring');
const { isTestableElement, formatCoverageInfo } = require('./../file-elements');
const { resultKind } = require('./../tests');
const { versionType } = require('./../project-versions');
const { type: groupType } = require('./../test-case-groups');
const utility = require('./../utility');
const BaseDataAdapter = require('./base-data-adapter');
const cacheUtil = require('../../cacheUtil');
// eslint-disable-next-line no-unused-vars
const ProgressIndicator = require('../progress-indicator');

/* eslint class-methods-use-this: ["error", { "exceptMethods": ["filterTestCasesByResultKind"] }] */
class ProjectVersionSpliter {
  constructor(versionId, version = null, project = null, creator = null, outputDir = null) {
    this.versionId = versionId;
    this.version = version;
    this.project = project;
    this.creator = creator;
    this.outputDir = outputDir;
    this.targetMachineDataSource = null;
    if (!this.outputDir) {
      this.outputDir = path.join(cacheUtil.getConfigPath('exportDir'), randomstring.generate(16));
    }
    if (process.platform === 'win32') {
      this.version.versionName = utility.handleWindwowFileNameSpecialCharacters(this.version.versionName);
      this.project.projectName = utility.handleWindwowFileNameSpecialCharacters(this.project.projectName);
    }
    this.setDataAdapter(new BaseDataAdapter());
  }

  setDataAdapter(adapter) {
    this.dataAdapter = adapter;
    return this;
  }

  getDataAdapter() {
    return this.dataAdapter;
  }

  // eslint-disable-next-line no-unused-vars,class-methods-use-this
  setTargetMachineDataSource(data) {
  }

  /**
   * 按项目版本分割
   * @param {Function} callback 回调，处理版本路径
   * @return {Promise}
   */
  spliteByProjectVersion(callback = null) {
    return new Promise((resolve, reject) => {
      const result = { projectDir: '', versionPath: '' };
      this.getDataAdapter().getCompleteVersionInfo(
        this.versionId,
        this.version,
        this.project,
        this.creator
      )
        .then(() => {
          result.projectDir = path.join(this.outputDir, this.project.projectName);
          result.versionPath = path.join(result.projectDir, this.version.versionName);
          result.versionType = this.version.versionType || versionType.unitTest;
          return fs.ensureDir(result.projectDir);
        })
        .then(() => {
          if (callback) {
            return callback(result.versionPath);
          }
          return this.handleVersion(result.versionPath);
        })
        .then(() => {
          resolve(result);
        })
        .catch(reject);
    });
  }

  /**
   * 按文件分割
   * @param {ProgressIndicator} progressIndicator 进度指示器
   * @return {Promise}
   */
  spliteByFile(callback = null, progressIndicator) {
    return new Promise((resolve, reject) => {
      const result = {};
      this.spliteByProjectVersion((versionDir) => fs.ensureDir(versionDir))
        .then(({ projectDir, versionPath }) => {
          result.projectDir = projectDir;
          result.versionPath = versionPath;
          result.versionDir = versionPath;
          if (this.reportRange && this.reportRange.indexOf(2) < 0) {
            return resolve(result);
          }
          return this.getDataAdapter().getFiles(this.versionId);
        })
        .then((resFiles) => this.handleFiles(resFiles, result.versionDir, callback, progressIndicator))
        .then(() => {
          resolve(result);
        })
        .catch(reject);
    });
  }

  /**
   * 按函数分割
   * @return {Promise}
   */
  spliteByFunction(callback = null, progressIndicator) {
    return new Promise((resolve, reject) => {
      this.spliteByFile((file, dir) => this.handleFunctionsOfFile(file, dir, callback), progressIndicator)
        .then(resolve)
        .catch(reject);
    });
  }

  /**
   * 项目版本处理
   * @param {String} versionPath 版本路径
   * @return {Promise}
   */
  handleVersion(versionPath) {
    return new Promise((resolve, reject) => {
      this.getDataAdapter().getCompleteVersionInfo(
        this.versionId,
        this.version,
        this.project,
        this.creator
      )
        .then(() => {
          resolve(versionPath);
        })
        .catch(reject);
    });
  }

  /**
   * 批量处理文件
   * @param {Array} files 文件列表
   * @param {String} versionDir 版本目录
   * @param {Function} callback 回调
   * @param {Number} index 下标
   * @return {Promise}
   */
  handleFilesBak(files, versionDir, callback = null, index = 0) {
    if (!files || index >= files.length) {
      return Promise.resolve();
    }
    return new Promise((resolve, reject) => {
      Promise.resolve()
        .then(() => {
          if (callback) {
            return callback(files[index], versionDir);
          }
          return this.handleFile(files[index], versionDir);
        })
        .then(() => this.handleFilesBak(files, versionDir, callback, index + 1))
        .then(resolve)
        .catch(reject);
    });
  }

  /**
   * 批量处理文件
   * @param {Array} files 文件列表
   * @param {String} versionDir 版本目录
   * @param {Function} callback 回调
   * @param {Number} index 下标
   * @param {ProgressIndicator} progressIndicator 进度指示器
   * @return {Promise}
   */
  handleFiles(files, versionDir, callback = null, progressIndicator = null) {
    return utility.arrayChunkOperation(files, 1, async (file) => {
      if (callback) {
        await callback(file, versionDir);
      }
      await this.handleFile(file, versionDir);
      if (progressIndicator) {
        progressIndicator.advance(1 / files.length);
      }
    });
  }

  filterTestCasesByResultKind(test) {
    return test.resultKind === resultKind.unknown;
  }

  /**
   * 文件处理
   * @param {Object} file 文件信息
   * @param {String} versionDir 版本目录
   * @return {Promise}
   */
  handleFile(file, versionDir) {
    return new Promise((resolve, reject) => {
      this.getDataAdapter().getCompleteVersionInfo(
        this.versionId,
        this.version,
        this.project,
        this.creator
      )
        .then(() => {
          // 获取类型系统、函数列表、函数变量列表、测试用例列表
          const fileId = file._id; // eslint-disable-line no-underscore-dangle
          const promises = [
            this.getDataAdapter().getFileTypes(fileId),
            this.getDataAdapter().getFunctions(this.versionId, fileId),
            this.getDataAdapter().getFileVariables(fileId, this.version._id),
            this.getDataAdapter().getFileTests(fileId),
            this.getDataAdapter().getUserDefinedFields(this.version.versionType),
            this.getDataAdapter().getFileTestCaseGroups(fileId),
            this.getDataAdapter().getCodeDefects(fileId),
          ];
          return Promise.all(promises);
        })
        .then(([
          resTypeSystem,
          resFunctions,
          resFunctionVariables,
          resTests, { list: udfs },
          testCaseGroups,
          codeDefects,
        ]) => {
          let queue = Promise.resolve();
          if (this.isIntegrationTest()) {
            resFunctions = resFunctions.filter((func) => isTestableElement(func.kind));
            resFunctionVariables = new Map();
            utility.arrayChunk(resFunctions, 5).forEach((funcs) => {
              queue = queue.then(
                () => Promise.all(
                  funcs.map((func) => this.getDataAdapter().getIntegratedFuncsVariables(func._id, this.versionId))
                )
                  .then((functionsVariable) => {
                    functionsVariable.forEach((functionVariable) => {
                      resFunctionVariables.set(
                        functionVariable.functionId.toString(),
                        functionVariable
                      );
                    });
                  })
              );
            });
            queue = queue
              .then(() => Promise.all(resTests.map((test) => this.getDataAdapter()
                .getIntegratedFilesByTest(
                  test._id,
                  test,
                  resFunctionVariables.get(test.functionId.toString())
                ))))
              .then((testsFileIds) => {
                resFunctionVariables = [...resFunctionVariables.values()];
                resTypeSystem = {};
                const fileIds = [...new Set([].concat(...testsFileIds))];
                if (fileIds.length === 0) {
                  return Promise.resolve();
                }
                return this.getDataAdapter().getFileTypes(fileIds)
                  .then((filesType) => {
                    const types = {};
                    filesType.forEach((fileType) => {
                      types[fileType.fileId.toString()] = fileType.types;
                    });
                    resTypeSystem = types;
                    return Promise.resolve();
                  });
              });
          }
          return queue.then(
            () => [
              resTypeSystem,
              resFunctions,
              resFunctionVariables,
              resTests,
              { list: udfs },
              testCaseGroups,
              codeDefects,
            ]
          );
        })
        .then(([
          resTypeSystem,
          resFunctions,
          resFunctionVariables,
          resTests,
          { list: udfs },
          testCaseGroups,
          codeDefects,
        ]) => {
          const groupMap = new Map();
          const testCaseGroupMap = new Map();
          testCaseGroups.forEach((testCaseGroup) => {
            testCaseGroup = testCaseGroup.toObject();
            groupMap.set(testCaseGroup._id.toString(), testCaseGroup);
            testCaseGroup.children.forEach((child) => {
              if (child.childType !== groupType.testCase) {
                return;
              }
              testCaseGroupMap.set(child.id.toString(), testCaseGroup.path);
            });
          });
          // 筛选出有执行数据的用例、函数
          let resultCount = 0;
          const functionMap = new Map();
          const functionUdfs = udfs.filter((u) => u.scope === 'function');
          const testUdfs = udfs.filter((u) => u.scope === 'testcase');
          resFunctions.forEach((func) => {
            formatCoverageInfo(func);
            const functionId = func._id.toString(); // eslint-disable-line no-underscore-dangle
            func.tests = [];
            // 过滤文件中函数的编组
            func.testCaseGroups = testCaseGroups.filter(
              (group) => group.functionId.toString() === functionId
            );
            func.filePath = file.path;
            functionMap.set(functionId, func);
            func.udfs = functionUdfs.map((u) => ({
              name: u.name,
              value: (func.userDefinedFields.find((u1) => u._id.equals(u1.key)) || {}).value,
            }));
            func.codeDefects = codeDefects.filter((cd) => cd.functionId.equals(func._id));
          });
          resFunctionVariables.forEach((variables) => {
            if (functionMap.has(variables.functionId.toString())) {
              functionMap.get(variables.functionId.toString()).variables = variables;
            }
          });
          function getTestCaseOrder(testId, groupId, order = 0) {
            let find = false;
            groupMap.get(groupId.toString()).children.forEach((child) => {
              if (find) {
                return;
              }
              if (child.childType !== groupType.testCase) {
                const childOrder = getTestCaseOrder(testId, child.id, order);
                find = childOrder.find;
                order = childOrder.order;
              } else {
                order += 1;
              }
              if (child.id.toString() === testId) {
                find = true;
              }
            });
            return { find, order };
          }
          resTests.forEach((test) => {
            test.testIdentifierRule = this.version.testIdentifierRule;
            test.filepath = file.path;
            test.functionName = functionMap.get(test.functionId.toString()).functionName;
            test.identifier = this.getDataAdapter().getTestIdentifier(test);
            if (test.requirementInfo === undefined) {
              test.requirementInfo = '';
            }
            test.udfs = testUdfs.map((u) => ({
              name: u.name,
              value: (test.userDefinedFields.find((u1) => u._id.equals(u1.key)) || {}).value,
            }));
            if (this.filterTestCasesByResultKind(test)) {
              return;
            }
            if (testCaseGroupMap.has(test._id.toString())) {
              const groupInfo = [];
              const groupPath = testCaseGroupMap.get(test._id.toString());
              groupPath.forEach((groupId) => {
                groupId = groupId.toString();
                if (!groupMap.has(groupId)) {
                  return;
                }
                groupInfo.push(groupMap.get(groupId).number);
              });
              test.group = groupInfo;
              test.weight = `${groupPath[0]}${getTestCaseOrder(test._id.toString(), groupPath[0]).order.toString().padStart(4, '0')}`;
            } else {
              test.weight = test.number;
            }
            resultCount += 1;
            functionMap.get(test.functionId.toString()).tests.push(test);
          });
          let queue = Promise.resolve();
          utility.arrayChunk(resFunctions, 20).forEach((funcsGroup) => {
            queue = queue.then(
              () => Promise.all(
                funcsGroup.map((func) => this.getDataAdapter().getIntegratedFuncs(func._id, true)
                  .then((integratedFuncs) => {
                    func.integrationFunctions = integratedFuncs
                      .map((relation, index) => ({ name: `${relation.toFuncName}${index === integratedFuncs.length - 1 ? '' : ','}` }));
                  }))
              )
            );
          });
          return queue
            .then(() => {
              resolve({
                file,
                versionDir,
                typeSystem: resTypeSystem,
                functions: resFunctions,
                resultCount,
              });
            });
        })
        .catch(reject);
    });
  }

  /**
   * 单个文件中的函数处理
   * @param {Object} file 文件信息
   * @param {String} versionDir 版本目录
   * @param {Function} callback 回调
   * @return {Promise}
   */
  handleFunctionsOfFile(file, versionDir, callback = null) {
    return new Promise((resolve, reject) => {
      this.handleFile(file, versionDir)
        .then(({ typeSystem, functions }) => {
          const promises = [];
          functions.forEach((func) => {
            if (!func.tests || func.tests.length === 0) {
              return;
            }
            if (callback) {
              promises.push(callback(func, file, typeSystem, versionDir));
            } else {
              promises.push(this.handleFunction(func, file, typeSystem, versionDir));
            }
          });
          return Promise.all(promises);
        })
        .then(resolve)
        .catch(reject);
    });
  }

  /**
   * 函数处理
   * @param {Object} func 函数信息
   * @param {Object} file 文件信息
   * @param {Object} typeSystem 类型系统
   * @param {String} versionDir 版本目录
   * @return {Promise}
   */
  handleFunction(func, file, typeSystem, versionDir) {
    return new Promise((resolve, reject) => {
      this.getDataAdapter().getCompleteVersionInfo(
        this.versionId,
        this.version,
        this.project,
        this.creator
      )
        .then(() => {
          resolve({
            func,
            file,
            typeSystem,
            versionDir,
          });
        })
        .catch(reject);
    });
  }

  isIntegrationTest() {
    return this.version.versionType === versionType.integrationTest;
  }

  isStaticAnalyzeTest() {
    return this.version.versionType === versionType.staticAnalyze;
  }
}

/**
 * Export model definition object.
 */
module.exports = ProjectVersionSpliter;
